<html><head><meta charset="utf-8"><title>31 AbstractQueuedSynchronizer 源码解析（下）-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        31 AbstractQueuedSynchronizer 源码解析（下）
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-11-08 11:04:36
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img4.mukewang.com/5dba92920001dea906400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">低头要有勇气，抬头要有底气。<p class="author">——韩寒</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h1 id="引导语">引导语</h1>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">AQS 的内容太多，所以我们分成了两个章节，没有看过 AQS 上半章节的同学可以回首看一下哈，上半章节里面说了很多锁的基本概念，基本属性，如何获得锁等等，本章我们主要聊下如何释放锁和同步队列两大部分。</p>
</div><div class="cl-preview-section"><h1 id="释放锁">1 释放锁</h1>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">释放锁的触发时机就是我们常用的 Lock.unLock () 方法，目的就是让线程释放对资源的访问权（流程见整体架构图紫色路线）。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">释放锁也是分为两类，一类是排它锁的释放，一类是共享锁的释放，我们分别来看下。</p>
</div><div class="cl-preview-section"><h2 id="释放排它锁-release" style="font-size: 30px;">1.1 释放排它锁 release</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">排它锁的释放就比较简单了，从队头开始，找它的下一个节点，如果下一个节点是空的，就会从尾开始，一直找到状态不是取消的节点，然后释放该节点，源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// unlock 的基础方法</span>
<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">release</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// tryRelease 交给实现类去实现，一般就是用当前同步器状态减去 arg，如果返回 true 说明成功释放锁。</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryRelease</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>
        <span class="token comment">// 头节点不为空，并且非初始化状态</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token comment">// 从头开始唤醒等待锁的节点</span>
            <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 很有意思的方法，当线程释放锁成功后，从 node 开始唤醒同步队列中的节点</span>
<span class="token comment">// 通过唤醒机制,保证线程不会一直在同步队列中阻塞等待</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// node 节点是当前释放锁的节点，也是同步队列的头节点</span>
    <span class="token keyword">int</span> ws <span class="token operator">=</span> node<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>
    <span class="token comment">// 如果节点已经被取消了，把节点的状态置为初始化</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 拿出 node 节点的后面一个节点</span>
    Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
    <span class="token comment">// s 为空，表示 node 的后一个节点为空</span>
    <span class="token comment">// s.waitStatus 大于0，代表 s 节点已经被取消了</span>
    <span class="token comment">// 遇到以上这两种情况，就从队尾开始，向前遍历，找到第一个 waitStatus 字段不是被取消的</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span>waitStatus <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        s <span class="token operator">=</span> null<span class="token punctuation">;</span>
        <span class="token comment">// 这里从尾迭代，而不是从头开始迭代是有原因的。</span>
        <span class="token comment">// 主要是因为节点被阻塞的时候，是在 acquireQueued 方法里面被阻塞的，唤醒时也一定会在 acquireQueued 方法里面被唤醒，唤醒之后的条件是，判断当前节点的前置节点是否是头节点，这里是判断当前节点的前置节点，所以这里必须使用从尾到头的迭代顺序才行，目的就是为了过滤掉无效的前置节点，不然节点被唤醒时，发现其前置节点还是无效节点，就又会陷入阻塞。</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span> t <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> t <span class="token operator">!=</span> node<span class="token punctuation">;</span> t <span class="token operator">=</span> t<span class="token punctuation">.</span>prev<span class="token punctuation">)</span>
            <span class="token comment">// t.waitStatus &lt;= 0 说明 t 没有被取消，肯定还在等待被唤醒</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
                s <span class="token operator">=</span> t<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 唤醒以上代码找到的线程</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">!=</span> null<span class="token punctuation">)</span>
        LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><h2 id="释放共享锁-releaseshared" style="font-size: 30px;">1.2 释放共享锁 releaseShared</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">释放共享锁的方法是 releaseShared，主要分成两步：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">tryReleaseShared 尝试释放当前共享锁，失败返回 false，成功走 2；</li>
<li style="font-size: 20px; line-height: 38px;">唤醒当前节点的后续阻塞节点，这个方法我们之前看过了，线程在获得共享锁的时候，就会去唤醒其后面的节点，方法名称为：doReleaseShared。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们一起来看下 releaseShared 的源码：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 共享模式下，释放当前线程的共享锁</span>
<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 这个方法就是线程在获得锁时，唤醒后续节点时调用的方法</span>
        <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><h1 id="条件队列的重要方法">2 条件队列的重要方法</h1>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在看条件队列的方法之前，我们先得弄明白为什么有了同步队列，还需要条件队列？</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">主要是因为并不是所有场景一个同步队列就可以搞定的，在遇到锁 + 队列结合的场景时，就需要 Lock + Condition 配合才行，先使用 Lock 来决定哪些线程可以获得锁，哪些线程需要到同步队列里面排队阻塞；获得锁的多个线程在碰到队列满或者空的时候，可以使用 Condition 来管理这些线程，让这些线程阻塞等待，然后在合适的时机后，被正常唤醒。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">同步队列 + 条件队列联手使用的场景，最多被使用到锁 + 队列的场景中。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">所以说条件队列也是不可或缺的一环。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">接下来我们来看一下条件队列一些比较重要的方法，以下方法都在  ConditionObject 内部类中。</p>
</div><div class="cl-preview-section"><h2 id="入队列等待-await" style="font-size: 30px;">2.1 入队列等待 await</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">获得锁的线程，如果在碰到队列满或空的时候，就会阻塞住，这个阻塞就是用条件队列实现的，这个动作我们叫做入条件队列，方法名称为 await，流程见整体架构图中深绿色箭头流向，我们一起来看下 await 的源码：</p>
</div><div class="cl-preview-section"><pre class=" language-java"><code class="prism  language-java"><span class="token comment">// 线程入条件队列</span>
<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 加入到条件队列的队尾</span>
    Node node <span class="token operator">=</span> <span class="token function">addConditionWaiter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 标记位置 A</span>
    <span class="token comment">// 加入条件队列后，会释放 lock 时申请的资源，唤醒同步队列队列头的节点</span>
    <span class="token comment">// 自己马上就要阻塞了，必须马上释放之前 lock 的资源，不然自己不被唤醒的话，别的线程永远得不到该共享资源了</span>
    <span class="token keyword">int</span> savedState <span class="token operator">=</span> <span class="token function">fullyRelease</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">int</span> interruptMode <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token comment">// 确认node不在同步队列上，再阻塞，如果 node 在同步队列上，是不能够上锁的</span>
    <span class="token comment">// 目前想到的只有两种可能：</span>
    <span class="token comment">// 1:node 刚被加入到条件队列中，立马就被其他线程 signal 转移到同步队列中去了</span>
    <span class="token comment">// 2:线程之前在条件队列中沉睡，被唤醒后加入到同步队列中去</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isOnSyncQueue</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// this = AbstractQueuedSynchronizer$ConditionObject</span>
        <span class="token comment">// 阻塞在条件队列上</span>
        LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>interruptMode <span class="token operator">=</span> <span class="token function">checkInterruptWhileWaiting</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">break</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 标记位置 B</span>
    <span class="token comment">// 其他线程通过 signal 已经把 node 从条件队列中转移到同步队列中的数据结构中去了</span>
    <span class="token comment">// 所以这里节点苏醒了，直接尝试 acquireQueued</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">acquireQueued</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> savedState<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> interruptMode <span class="token operator">!=</span> THROW_IE<span class="token punctuation">)</span>
        interruptMode <span class="token operator">=</span> REINTERRUPT<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>nextWaiter <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token comment">// clean up if cancelled</span>
        <span class="token comment">// 如果状态不是CONDITION，就会自动删除</span>
        <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>interruptMode <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token function">reportInterruptAfterWait</span><span class="token punctuation">(</span>interruptMode<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">await 方法有几点需要特别注意：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">上述代码标记位置 A 处，节点在准备进入条件队列之前，一定会先释放当前持有的锁，不然自己进去条件队列了，其余的线程都无法获得锁了；</li>
<li style="font-size: 20px; line-height: 38px;">上述代码标记位置 B 处，此时节点是被 Condition.signal 或者 signalAll 方法唤醒的，此时节点已经成功的被转移到同步队列中去了（整体架构图中蓝色流程），所以可以直接执行 acquireQueued 方法；</li>
<li style="font-size: 20px; line-height: 38px;">Node 在条件队列中的命名，源码喜欢用 Waiter 来命名，所以我们在条件队列中看到 Waiter，其实就是 Node。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">await 方法中有两个重要方法：addConditionWaiter 和 unlinkCancelledWaiters，我们一一看下。</p>
</div><div class="cl-preview-section"><h3 id="addconditionwaiter">2.1.1 addConditionWaiter</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">addConditionWaiter 方法主要是把节点放到条件队列中，方法源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 增加新的 waiter 到队列中，返回新添加的 waiter</span>
<span class="token comment">// 如果尾节点状态不是 CONDITION 状态，删除条件队列中所有状态不是 CONDITION 的节点</span>
<span class="token comment">// 如果队列为空，新增节点作为队列头节点，否则追加到尾节点上</span>
<span class="token keyword">private</span> Node <span class="token function">addConditionWaiter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    Node t <span class="token operator">=</span> lastWaiter<span class="token punctuation">;</span>
    <span class="token comment">// If lastWaiter is cancelled, clean out.</span>
    <span class="token comment">// 如果尾部的 waiter 不是 CONDITION 状态了，删除</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> t<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        t <span class="token operator">=</span> lastWaiter<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 新建条件队列 node</span>
    Node node <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 队列是空的，直接放到队列头</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> null<span class="token punctuation">)</span>
        firstWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span>
    <span class="token comment">// 队列不为空，直接到队列尾部</span>
    <span class="token keyword">else</span>
        t<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span>
    lastWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span>
    <span class="token keyword">return</span> node<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">整体过程比较简单，就是追加到队列的尾部，其中有个重要方法叫做 unlinkCancelledWaiters，这个方法会删除掉条件队列中状态不是 CONDITION 的所有节点，我们来看下 unlinkCancelledWaiters 方法的源码，如下：</p>
</div><div class="cl-preview-section"><h3 id="unlinkcancelledwaiters">2.1.2 unlinkCancelledWaiters</h3>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 会检查尾部的 waiter 是不是已经不是CONDITION状态了</span>
<span class="token comment">// 如果不是，删除这些 waiter</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    Node t <span class="token operator">=</span> firstWaiter<span class="token punctuation">;</span>
    <span class="token comment">// trail 表示上一个状态,这个字段作用非常大，可以把状态都是 CONDITION 的 node 串联起来，即使 node 之间有其他节点都可以</span>
    Node trail <span class="token operator">=</span> null<span class="token punctuation">;</span>
    <span class="token keyword">while</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        Node next <span class="token operator">=</span> t<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">;</span>
        <span class="token comment">// 当前node的状态不是CONDITION，删除自己</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">//删除当前node</span>
            t<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>
            <span class="token comment">// 如果 trail 是空的，咱们循环又是从头开始的，说明从头到当前节点的状态都不是 CONDITION</span>
            <span class="token comment">// 都已经被删除了，所以移动队列头节点到当前节点的下一个节点</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>trail <span class="token operator">==</span> null<span class="token punctuation">)</span>
                firstWaiter <span class="token operator">=</span> next<span class="token punctuation">;</span>
            <span class="token comment">// 如果找到上次状态是CONDITION的节点的话，先把当前节点删掉，然后把自己挂到上一个状态是 CONDITION 的节点上</span>
            <span class="token keyword">else</span>
                trail<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> next<span class="token punctuation">;</span>
            <span class="token comment">// 遍历结束，最后一次找到的CONDITION节点就是尾节点</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>next <span class="token operator">==</span> null<span class="token punctuation">)</span>
                lastWaiter <span class="token operator">=</span> trail<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">// 状态是 CONDITION 的 Node</span>
        <span class="token keyword">else</span>
            trail <span class="token operator">=</span> t<span class="token punctuation">;</span>
        <span class="token comment">// 继续循环，循环顺序从头到尾</span>
        t <span class="token operator">=</span> next<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">为了方便大家理解这个方法，画了一个释义图，如下：<br>
<img class="" src="https://img.mukewang.com/5dba408600017eaf07211157.png" data-original="//img.mukewang.com/5dba408600017eaf07211157.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><h2 id="单个唤醒-signal" style="font-size: 30px;">2.2 单个唤醒 signal</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">signal 方法是唤醒的意思，比如之前队列满了，有了一些线程因为 take 操作而被阻塞进条件队列中，突然队列中的元素被线程 A 消费了，线程 A 就会调用 signal 方法，唤醒之前阻塞的线程，会从条件队列的头节点开始唤醒（流程见整体架构图中蓝色部分），源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 唤醒阻塞在条件队列中的节点</span>
<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 从头节点开始唤醒</span>
    Node first <span class="token operator">=</span> firstWaiter<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>first <span class="token operator">!=</span> null<span class="token punctuation">)</span>
        <span class="token comment">// doSignal 方法会把条件队列中的节点转移到同步队列中去</span>
        <span class="token function">doSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 把条件队列头节点转移到同步队列去</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doSignal</span><span class="token punctuation">(</span>Node first<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">do</span> <span class="token punctuation">{</span>
        <span class="token comment">// nextWaiter为空，说明到队尾了</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span>firstWaiter <span class="token operator">=</span> first<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>
            lastWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>
        <span class="token comment">// 从队列头部开始唤醒，所以直接把头节点.next 置为 null，这种操作其实就是把 node 从条件队列中移除了</span>
        <span class="token comment">// 这里有个重要的点是，每次唤醒都是从队列头部开始唤醒，所以把 next 置为 null 没有关系，如果唤醒是从任意节点开始唤醒的话，就会有问题，容易造成链表的割裂</span>
        first<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>
        <span class="token comment">// transferForSignal 方法会把节点转移到同步队列中去</span>
        <span class="token comment">// 通过 while 保证 transferForSignal 能成功</span>
        <span class="token comment">// 等待队列的 node 不用管他，在 await 的时候，会自动清除状态不是 Condition 的节点(通过 unlinkCancelledWaiters 方法)</span>
        <span class="token comment">// (first = firstWaiter) != null  = true 的话，表示还可以继续循环， = false 说明队列中的元素已经循环完了</span>
    <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">transferForSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>
             <span class="token punctuation">(</span>first <span class="token operator">=</span> firstWaiter<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>   
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们来看下最关键的方法：transferForSignal。</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 返回 true 表示转移成功， false 失败</span>
<span class="token comment">// 大概思路：</span>
<span class="token comment">// 1. node 追加到同步队列的队尾</span>
<span class="token comment">// 2. 将 node 的前一个节点状态置为 SIGNAL，成功直接返回，失败直接唤醒</span>
<span class="token comment">// 可以看出来 node 的状态此时是 0 了</span>
<span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">transferForSignal</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">/*
     * If cannot change waitStatus, the node has been cancelled.
     */</span>
    <span class="token comment">// 将 node 的状态从 CONDITION 修改成初始化，失败返回 false</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>

    <span class="token comment">// 当前队列加入到同步队列，返回的 p 是 node 在同步队列中的前一个节点</span>
    <span class="token comment">// 看命名是 p，实际是 pre 单词的缩写</span>
    Node p <span class="token operator">=</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">int</span> ws <span class="token operator">=</span> p<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>
    <span class="token comment">// 状态修改成 SIGNAL，如果成功直接返回</span>
    <span class="token comment">// 把当前节点的前一个节点修改成 SIGNAL 的原因，是因为 SIGNAL 本身就表示当前节点后面的节点都是需要被唤醒的</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token comment">// 如果 p 节点被取消，或者状态不能修改成SIGNAL，直接唤醒</span>
        LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">整个源码下来，我们可以看到，唤醒条件队列中的节点，实际上就是把条件队列中的节点转移到同步队列中，并把其前置节点状态置为 SIGNAL。</p>
</div><div class="cl-preview-section"><h2 id="全部唤醒-signalall" style="font-size: 30px;">2.3 全部唤醒 signalAll</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">signalAll 的作用是唤醒条件队列中的全部节点，源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java">    <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">signalAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 拿到头节点</span>
        Node first <span class="token operator">=</span> firstWaiter<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>first <span class="token operator">!=</span> null<span class="token punctuation">)</span>
            <span class="token comment">// 从头节点开始唤醒条件队列中所有的节点</span>
            <span class="token function">doSignalAll</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 把条件队列所有节点依次转移到同步队列去</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doSignalAll</span><span class="token punctuation">(</span>Node first<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        lastWaiter <span class="token operator">=</span> firstWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>
        <span class="token keyword">do</span> <span class="token punctuation">{</span>
            <span class="token comment">// 拿出条件队列队列头节点的下一个节点</span>
            Node next <span class="token operator">=</span> first<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">;</span>
            <span class="token comment">// 把头节点从条件队列中删除</span>
            first<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>
            <span class="token comment">// 头节点转移到同步队列中去</span>
            <span class="token function">transferForSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 开始循环头节点的下一个节点</span>
            first <span class="token operator">=</span> next<span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>first <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从源码中可以看出，其本质就是 for 循环调用 transferForSignal 方法，将条件队列中的节点循环转移到同步队列中去。</p>
</div><div class="cl-preview-section"><h1 id="总结">3 总结</h1>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">AQS 源码终于说完了，你都懂了么，可以在默默回忆一下 AQS 架构图，看看这张图现在能不能看懂了。<img class="" src="https://img.mukewang.com/5dba40350001cb6f21120846.png" data-original="//img.mukewang.com/5dba40350001cb6f21120846.png" alt="图片描述"></p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/872">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            30 AbstractQueuedSynchronizer 源码解析（上）
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/874">
                                                                    <div class="next r clearfix">
                                        <p>
                                            32 ReentrantLock 源码解析 
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img1.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>